using System;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Input;

namespace HDViewSL
{
	/// <summary>
	/// Helper class that listens for browser mouse wheel events and distributes them to elements.
	/// </summary>
	/// <remarks>This is a modified version of code provided by Pete Blois.</remarks>
	public class MouseWheelHelper
	{
		/// <summary>
		/// Raised when the mouse wheel is rotated while the mouse is over the associated element.
		/// </summary>
		public event EventHandler<MouseWheelEventArgs> MouseWheel;

		private static Worker worker;
		private bool isMouseOver;

		/// <summary>
		/// Constructs a new MouseWheelHelper associated with the given element.
		/// </summary>
		/// <param name="element">The element to associate with this helper.</param>
		public MouseWheelHelper(FrameworkElement element)
		{
			if (MouseWheelHelper.worker == null)
			{
				MouseWheelHelper.worker = new Worker();
			}

			MouseWheelHelper.worker.MouseWheel += this.Worker_MouseWheel;

			element.MouseEnter += this.Element_MouseEnter;
			element.MouseMove += this.Element_MouseMove;
			element.MouseLeave += this.Element_MouseLeave;
		}

		/// <summary>
		/// Raises the MouseWheel event if the mouse is over the associated element.
		/// </summary>
		private void Worker_MouseWheel(object sender, MouseWheelEventArgs args)
		{
			var handler = this.MouseWheel;
			if (this.isMouseOver && handler != null)
			{
				handler(this, args);
			}
		}

		/// <summary>
		/// Handles the MouseEnter event for the associated element.
		/// </summary>
		private void Element_MouseEnter(object sender, MouseEventArgs e)
		{
			this.isMouseOver = true;
		}

		/// <summary>
		/// Handles the MouseMove event for the associated element.
		/// </summary>
		private void Element_MouseMove(object sender, MouseEventArgs e)
		{
			this.isMouseOver = true;
		}

		/// <summary>
		/// Handles the MouseLeave event for the associated element.
		/// </summary>
		private void Element_MouseLeave(object sender, MouseEventArgs e)
		{
			this.isMouseOver = false;
		}

		/// <summary>
		/// A worker class that deals with the idiosyncracies of mouse wheel events in various browsers.
		/// </summary>
		private class Worker
		{
			public event EventHandler<MouseWheelEventArgs> MouseWheel;

			private double multiplier = 1;

			public Worker()
			{
				if (HtmlPage.IsEnabled)
				{
					HtmlPage.Window.AttachEvent("DOMMouseScroll", this.HandleMouseWheel);
					HtmlPage.Window.AttachEvent("onmousewheel", this.HandleMouseWheel);
					HtmlPage.Document.AttachEvent("onmousewheel", this.HandleMouseWheel);

					// Flip the sign of delta values on versions of Opera before version 9.2.
					string userAgent = HtmlPage.BrowserInformation.UserAgent;
					Match match = new Regex(@"Opera[/ ]([\d]+.[\d]+)").Match(userAgent);
					double operaVersion;
					if (match.Success &&
						Double.TryParse(match.Groups[1].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out operaVersion) &&
						operaVersion < 9.2)
					{
						this.multiplier = -1;
					}

					// Triple the delta values on Mac machines.
					if (userAgent.Contains("Macintosh"))
					{
						this.multiplier *= 3;
					}
				}
			}

			private void HandleMouseWheel(object sender, HtmlEventArgs args)
			{
				ScriptObject eventObject = args.EventObject;
				object wheelDelta = eventObject.GetProperty("wheelDelta");
				object detail = eventObject.GetProperty("detail");

				double delta = 0;
				if (wheelDelta != null)
				{
					delta = (double)wheelDelta / 120;
				}
				else if (detail != null)
				{
					delta = (double)detail / -3;
				}
				delta *= this.multiplier;

				var handler = this.MouseWheel;
				if (delta != 0 && handler != null)
				{
					MouseWheelEventArgs wheelArgs = new MouseWheelEventArgs(delta);
					handler(this, wheelArgs);

					if (wheelArgs.Handled)
					{
						args.PreventDefault();
					}
				}
			}
		}
	}

	/// <summary>
	/// Event arguments for mouse wheel events.
	/// </summary>
	public class MouseWheelEventArgs : EventArgs
	{
		/// <summary>
		/// Constructs a new MouseWheelEventArgs object.
		/// </summary>
		/// <param name="delta">The mouse wheel change.</param>
		public MouseWheelEventArgs(double delta)
		{
			this.Delta = delta;
		}

		/// <summary>
		/// The mouse wheel change.
		/// </summary>
		public double Delta { get; private set; }

		/// <summary>
		/// A flag indicating whether the event has been handled.
		/// </summary>
		/// <remarks>Set this flag to true to prevent the default browser behavior.</remarks>
		public bool Handled { get; set; }
	}
}